#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#include "UIAccelerometer_p.h"
#include "UIAccelerometer.h"

#define NEO_ACCELEROMETER_DEVICE1       "/dev/input/event2"
#define NEO_ACCELEROMETER_DEVICE2       "/dev/input/event3"

#define ACCELEROMETER_EVENT_CODE_X      0x00
#define ACCELEROMETER_EVENT_CODE_Y      0x01
#define ACCELEROMETER_EVENT_CODE_Z      0x02

#define ACCELEROMETER_BUFFER            0x8

// ===========================================================================
//  PRIVATE Accelerometer Reader
// ===========================================================================
UIAccelerometerReader::UIAccelerometerReader (const char *deviceName) {
    m_fd = open(deviceName, O_RDONLY);
}

UIAccelerometerReader::~UIAccelerometerReader() {
    if (m_fd >= 0) close(m_fd);
}

bool UIAccelerometerReader::canRead (void) const {
    return(m_fd >= 0);
}

#include <QDebug>
bool UIAccelerometerReader::processData (int *x, int *y, int *z) {
    int data[] = { 0, 0, 0 };
    bool retval = true;

    *x = *y = *z = 0;
    for (int i = 0; i < ACCELEROMETER_BUFFER; ++i) {
        if (!readData(&(data[0]), &(data[1]), &(data[2]))) {
            retval = (i > 0);
            break;
        }

        *x += data[0];
        *y += data[1];
        *z += data[2];
    }

    *x /= ACCELEROMETER_BUFFER;
    *y /= ACCELEROMETER_BUFFER;
    *z /= ACCELEROMETER_BUFFER;

    return(retval);
}

bool UIAccelerometerReader::readData (int *x, int *y, int *z) {
    UIAccelerometerEventData eventData;

    do {
        if (!readAccelerometerEventData(&eventData))
            return(false);

        if (eventData.code != 0x00) {
            if (eventData.type == ACCELEROMETER_EVENT_CODE_X)
                *x = eventData.value;
            else if (eventData.type == ACCELEROMETER_EVENT_CODE_Y)
                *y = eventData.value;
            else if (eventData.type == ACCELEROMETER_EVENT_CODE_Z)
                *z = eventData.value;
        }
    } while (eventData.code != 0x00);

    return(true);
}

bool UIAccelerometerReader::readAccelerometerEventData (
    UIAccelerometerEventData *eventData)
{
    uint nread = 0;
    int n;

    while (nread < sizeof(UIAccelerometerEventData)) {
        if ((n = read(m_fd, (char *)eventData + nread, 
                      sizeof(UIAccelerometerEventData) - nread)) < 0)
        {
            return(false);
        }

        if (n == 0)
            break;

        nread += n;
    }

    return(nread == sizeof(UIAccelerometerEventData));
}

// ===========================================================================
//  PRIVATE Accelerometer Thread
// ===========================================================================
UIAccelerometerThread::UIAccelerometerThread (QObject *parent)
    : QThread(parent)
{
    m_abort = false;
    m_cx = m_cy = m_cz = 0;
    m_updateInterval = 16666;		// (1.0 / 60) * 1000000sec
}

UIAccelerometerThread::~UIAccelerometerThread() {
    m_abort = true;
    qDebug("KILL Accelerometer Thread");
}

void UIAccelerometerThread::run (void) {
    UIAccelerometerReader reader(NEO_ACCELEROMETER_DEVICE2);
    int x, y, z;

    while (!m_abort) {
        if (reader.processData(&x, &y, &z)) {
			// Average with cached values
			x = m_cx = (x + m_cx) / 2;
			y = m_cy = (y + m_cy) / 2;
			z = m_cz = (z + m_cz) / 2;

			// Convert Moko Values in -1.00 - 1.00 Range
			qreal dx = (qreal(x) / 1000.0);
			qreal dy = (qreal(y) / 1000.0);
			qreal dz = (qreal(z) / 1000.0);

#if 0
			dx = dx * 0.05 + dx * (1.0 - 0.05);
			dy = dy * 0.05 + dy * (1.0 - 0.05);
			dz = dz * 0.05 + dz * (1.0 - 0.05);
#endif

            emit accelerate(dx, dy, dz);
        }

        usleep(m_updateInterval);
    }

    m_abort = false;
}

// ===========================================================================
//  PUBLIC UIAcceleration
// ===========================================================================
class UIAccelerationPrivate {
    public:
        UIAccelerationValue x, y, z;
        qreal timestamp;
};

UIAcceleration::UIAcceleration() 
    : d(new UIAccelerationPrivate)
{
    d->x = d->y = d->z = -2.0f;
    d->timestamp = 0;
}

UIAcceleration::~UIAcceleration() {
    delete d;
}

/** Returns True if Acceleration is Not set */
bool UIAcceleration::isNull (void) const {
    return(d->x == -2.0f);
}

/** 
 * With the device held in portrait orientation and the screen facing you, 
 * the x axis runs from left to right across the face of the device. 
 */
UIAccelerationValue UIAcceleration::x (void) const {
    return(d->x);
}

/**
 * With the device held in portrait orientation and the screen facing you, 
 * the y axis runs from top to bottom across the face of the device.
 */
UIAccelerationValue UIAcceleration::y (void) const {
    return(d->y);
}

/**
 * With the device held in portrait orientation and the screen facing you, 
 * the z axis runs from back to front through the device.
 */
UIAccelerationValue UIAcceleration::z (void) const {
    return(d->z);
}

/** Acceleration Timestamp */
qreal UIAcceleration::timestamp (void) const {
    return(d->timestamp);
}

void UIAcceleration::setAcceleration (UIAccelerationValue x, 
                                      UIAccelerationValue y, 
                                      UIAccelerationValue z)
{
    d->x = x;
    d->y = y;
    d->z = z;
}

void UIAcceleration::setAcceleration (const UIAcceleration& acceleration) {
    d->x = acceleration.x();
    d->y = acceleration.y();
    d->z = acceleration.z();
}

class UIAccelerometerPrivate {
    public:
        UIAccelerometerThread *thread;
        UIAcceleration lastAcceleration;
        UIAcceleration acceleration;
        bool histeresisExcited;

    public:
        bool isShakeEvent (UIAccelerationValue x, 
                           UIAccelerationValue y, 
                           UIAccelerationValue z,
                           qreal threshold);
        bool isShaking (UIAccelerationValue x, 
                        UIAccelerationValue y, 
                        UIAccelerationValue z,
                        qreal threshold);
};

bool UIAccelerometerPrivate::isShakeEvent (UIAccelerationValue x, 
                                           UIAccelerationValue y, 
                                           UIAccelerationValue z, 
                                           qreal threshold)
{
    UIAccelerationValue dx = qAbs(acceleration.x() - x);
    UIAccelerationValue dy = qAbs(acceleration.y() - y);
    UIAccelerationValue dz = qAbs(acceleration.z() - z);

    return((dx > threshold && dy > threshold) ||
           (dx > threshold && dz > threshold) ||
           (dy > threshold && dz > threshold));
}

bool UIAccelerometerPrivate::isShaking (UIAccelerationValue x, 
                                        UIAccelerationValue y, 
                                        UIAccelerationValue z,
                                        qreal threshold)
{
    if (qAbs(acceleration.x() - x) > threshold)
        return(true);

    if (qAbs(acceleration.y() - y) > threshold)
        return(true);

    if (qAbs(acceleration.z() - z) > threshold)
        return(true);

    return(false);
}

// ===========================================================================
//  PUBLIC GLOBAL STATIC Instances
// ===========================================================================
Q_GLOBAL_STATIC(UIAccelerometer, uiAccelerometer);

// ===========================================================================
//  PUBLIC STATIC Methods
// ===========================================================================
UIAccelerometer *UIAccelerometer::sharedAccelerometer (void) {
    return(uiAccelerometer());
}

// ===========================================================================
//  PUBLIC Properties
// ===========================================================================
qreal UIAccelerometer::updateInterval (void) const {
    return(qreal(d->thread->updateInterval()) / 1000000.0f);
}

void UIAccelerometer::setUpdateInterval (qreal secs) {
    d->thread->setUpdateInterval((unsigned long int)(secs * 1000000.0f));
}

bool UIAccelerometer::enabled (void) const {
    return(d->thread->isRunning());
}

void UIAccelerometer::setEnabled (bool enabled) {
    bool isRunning = d->thread->isRunning();

    if (!enabled && isRunning)
        d->thread->abort();
    else if (!isRunning && enabled)
        d->thread->start(QThread::LowPriority);
}

bool UIAccelerometer::isShaking (void) const {
    return(d->histeresisExcited && d->isShaking(d->acceleration.x(), 
                                                d->acceleration.y(), 
                                                d->acceleration.z(), 
                                                0.40f));
}

bool UIAccelerometer::isShaking (qreal threshold) const {
    return(d->histeresisExcited && d->isShaking(d->acceleration.x(), 
                                                d->acceleration.y(), 
                                                d->acceleration.z(), 
                                                threshold));
}

UIAcceleration *UIAccelerometer::acceleration (void) const {
    return(&(d->acceleration));
}

void UIAccelerometer::acceleration (UIAccelerationValue *x,
                                    UIAccelerationValue *y,
                                    UIAccelerationValue *z) const
{
    if (x != NULL) *x = d->acceleration.x();
    if (y != NULL) *y = d->acceleration.y();
    if (z != NULL) *z = d->acceleration.z();
}

// ===========================================================================
//  PRIVATE Slots
// ===========================================================================
void UIAccelerometer::accelerate (qreal x, qreal y, qreal z) {
    // Handle Shake Event
    if (!d->acceleration.isNull()) {
        if (!d->histeresisExcited && d->isShakeEvent(x, y, z, 0.6f)) {
            qDebug("Device Shaked");
            emit shaked();
        } else if (!d->isShakeEvent(x, y, z, 0.2f)) {
            d->histeresisExcited = false;
        }
    }

    // Update Acceleration and Raise 'accelerate' signal
    d->lastAcceleration.setAcceleration(d->acceleration);
    d->acceleration.setAcceleration(x, y, z);
    emit accelerate(&(d->acceleration));
}

// ===========================================================================
//  PRIVATE Constructor/Destructor
// ===========================================================================
UIAccelerometer::UIAccelerometer (QObject *parent)
    : QObject(parent), d(new UIAccelerometerPrivate)
{
    d->histeresisExcited = false;
    d->thread = new UIAccelerometerThread(this);
    connect(d->thread, SIGNAL(accelerate(qreal, qreal, qreal)),
            this, SLOT(accelerate(qreal, qreal, qreal)));
}

UIAccelerometer::~UIAccelerometer() {
    delete d;
}

